package spring.fox.processor;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.PropertyValues;
import org.springframework.beans.TypeConverter;
import org.springframework.beans.factory.*;
import org.springframework.beans.factory.annotation.InjectionMetadata;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.config.DependencyDescriptor;
import org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessorAdapter;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.MergedBeanDefinitionPostProcessor;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.core.BridgeMethodResolver;
import org.springframework.core.MethodParameter;
import org.springframework.core.Ordered;
import org.springframework.core.PriorityOrdered;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;
import spring.fox.bean.RpcServiceBean;

import java.beans.PropertyDescriptor;
import java.lang.annotation.Annotation;
import java.lang.reflect.*;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author cuichao
 * @Description: RpcAnnotationBeanPostProcessor
 * @Date: create in 2021/3/6 22:15
 */
public abstract class RpcAnnotationBeanPostProcessor<A extends Annotation>
        extends InstantiationAwareBeanPostProcessorAdapter
        implements MergedBeanDefinitionPostProcessor, BeanFactoryAware, PriorityOrdered, BeanClassLoaderAware {

    private final Log logger = LogFactory.getLog(getClass());

    private Class<A> annotationType;

    private int order = Ordered.LOWEST_PRECEDENCE - 3;

    private BeanDefinitionRegistry beanDefinitionRegistry;

    private ConfigurableListableBeanFactory beanFactory;

    private ClassLoader classLoader;

    private final Map<String, InjectionMetadata> injectionMetadataCache = new ConcurrentHashMap<>(256);


    public RpcAnnotationBeanPostProcessor() {
        resolveAnnotationType();
    }


    private void resolveAnnotationType() {
        ParameterizedType parameterizedType = (ParameterizedType) this.getClass().getGenericSuperclass();
        Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
        this.annotationType = ((Class<A>) actualTypeArguments[0]);
    }

    /**
     * 解析bean name
     *
     * @param member
     * @param injectType
     * @param annotation
     * @return
     */
    protected abstract String resolveBeanName(Member member, Class<?> injectType, Annotation annotation);

    /**
     * 构建beanDefinition
     *
     * @param annotation
     * @param bean
     * @param beanName
     * @param injectedType
     * @param injectName
     * @return
     */
    protected abstract BeanDefinition buildRpcBeanDefinition(Annotation annotation, Object bean, String beanName, Class<?> injectedType, String injectName);


    @Override
    public PropertyValues postProcessPropertyValues(PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName) throws BeansException {
        return postProcessProperties(pvs, bean, beanName);
    }

    @Override
    public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) throws BeansException {
        InjectionMetadata metadata = findInjectionMetadata(beanName, bean.getClass(), pvs);
        try {
            metadata.inject(bean, beanName, pvs);
        } catch (BeanCreationException ex) {
            throw ex;
        } catch (Throwable ex) {
            throw new BeanCreationException(beanName, "Injection of @" + annotationType.getName()
                    + " dependencies is failed", ex);
        }
        return pvs;
    }

    @Override
    public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName) {
        InjectionMetadata metadata = findInjectionMetadata(beanName, beanType, null);
        metadata.checkConfigMembers(beanDefinition);
    }

    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        if (!(beanFactory instanceof ConfigurableListableBeanFactory)) {
            throw new IllegalArgumentException(
                    this.getClass().getSimpleName() + " requires a ConfigurableListableBeanFactory: " + beanFactory);
        }
        this.beanFactory = (ConfigurableListableBeanFactory) beanFactory;

        if (!(beanFactory instanceof BeanDefinitionRegistry)) {
            throw new IllegalStateException(this.getClass().getSimpleName() + ": Could not locate BeanDefinitionRegistry");
        }
        this.beanDefinitionRegistry = (BeanDefinitionRegistry) beanFactory;
    }

    @Override
    public int getOrder() {
        return order;
    }

    /**
     * 查询/创建赋值元数据
     *
     * @param beanName
     * @param clazz
     * @param pvs
     * @return
     */
    private InjectionMetadata findInjectionMetadata(String beanName, Class<?> clazz, @Nullable PropertyValues pvs) {
        if (!AnnotationUtils.isCandidateClass(clazz, annotationType)) {
            return InjectionMetadata.EMPTY;
        }

        // Fall back to class name as cache key, for backwards compatibility with custom callers.
        String cacheKey = (StringUtils.hasLength(beanName) ? beanName : clazz.getName());
        // Quick check on the concurrent map first, with minimal locking.
        InjectionMetadata metadata = this.injectionMetadataCache.get(cacheKey);
        if (InjectionMetadata.needsRefresh(metadata, clazz)) {
            synchronized (this.injectionMetadataCache) {
                metadata = this.injectionMetadataCache.get(cacheKey);
                if (InjectionMetadata.needsRefresh(metadata, clazz)) {
                    if (metadata != null) {
                        metadata.clear(pvs);
                    }
                    metadata = buildInjectionMetadata(clazz);
                    this.injectionMetadataCache.put(cacheKey, metadata);
                }
            }
        }
        return metadata;
    }


    /**
     * 构建被注入类元数据
     *
     * @param clazz
     * @return
     */
    private RpcAnnotationBeanPostProcessor.RpcInjectionMetadata buildInjectionMetadata(Class<?> clazz) {
        List<InjectionMetadata.InjectedElement> elements = new ArrayList<>();

        List<InjectionMetadata.InjectedElement> fieldElements = buildInjectionFieldElement(clazz);

        if (!fieldElements.isEmpty()) {
            elements.addAll(fieldElements);
        }

        List<InjectionMetadata.InjectedElement> methodElements = buildInjectionMethodElement(clazz);

        if (!methodElements.isEmpty()) {
            elements.addAll(methodElements);
        }

        return new RpcInjectionMetadata(clazz, elements);
    }


    private List<InjectionMetadata.InjectedElement> buildInjectionFieldElement(Class<?> clazz) {
        List<InjectionMetadata.InjectedElement> elements = new ArrayList<>();
        //反射字段过滤器
        ReflectionUtils.doWithFields(clazz, field -> {
            A annotation = AnnotationUtils.getAnnotation(field, annotationType);
            if (annotation == null) {
                return;
            }
            //不支持静态字段
            if (Modifier.isStatic(field.getModifiers())) {
                if (logger.isWarnEnabled()) {
                    logger.warn("@" + annotationType.getName() + " is not supported on static fields: " + field);
                }
                return;
            }

            elements.add(new RpcFieldElement(field, annotation, true));
        });
        return elements;
    }

    private List<InjectionMetadata.InjectedElement> buildInjectionMethodElement(Class<?> clazz) {
        List<InjectionMetadata.InjectedElement> elements = new ArrayList<>();
        //反射方法过滤器
        ReflectionUtils.doWithMethods(clazz, method -> {

            Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(method);

            if (!BridgeMethodResolver.isVisibilityBridgeMethodPair(method, bridgedMethod)) {
                return;
            }

            Annotation annotation = AnnotationUtils.getAnnotation(method, annotationType);

            if (annotation == null || !method.equals(ClassUtils.getMostSpecificMethod(method, clazz))) {
                return;
            }
            //不支持静态方法
            if (Modifier.isStatic(method.getModifiers())) {
                if (logger.isWarnEnabled()) {
                    logger.warn("@" + annotationType.getName() + " is not supported on static methods: " + method);
                }
                return;
            }

            if (method.getParameterTypes().length == 0) {
                if (logger.isWarnEnabled()) {
                    logger.warn("@" + annotationType.getSimpleName() + " annotation should only be used on methods with parameters: " +
                            method);
                }
                return;
            }
            PropertyDescriptor pd = BeanUtils.findPropertyForMethod(bridgedMethod, clazz);
            elements.add(new RpcMethodElement(method, pd, true, annotation));
        });
        return elements;
    }


    /**
     * 被注入的bean与注入的bean建立依赖关系
     *
     * @param beanName
     * @param autowiredBeanNames
     */
    private void registerDependentBeans(@Nullable String beanName, Set<String> autowiredBeanNames) {
        if (beanName != null) {
            for (String autowiredBeanName : autowiredBeanNames) {
                if (this.beanFactory != null && this.beanFactory.containsBean(autowiredBeanName)) {
                    this.beanFactory.registerDependentBean(autowiredBeanName, beanName);
                }
                if (logger.isTraceEnabled()) {
                    logger.trace("RpcReference by type from bean name '" + beanName + "' to bean named '" + autowiredBeanName + "'");
                }
            }
        }
    }


    /**
     * 得到和背注入的bean关联关系的bean
     *
     * @param beanName
     * @param cachedArgument
     * @return
     */
    @Nullable
    private Object resolvedCachedArgument(@Nullable String beanName, @Nullable Object cachedArgument) {
        if (cachedArgument instanceof DependencyDescriptor) {
            DependencyDescriptor descriptor = (DependencyDescriptor) cachedArgument;
            Assert.state(this.beanFactory != null, "No BeanFactory available");
            return this.beanFactory.resolveDependency(descriptor, beanName, null, null);
        } else {
            return cachedArgument;
        }
    }

    /**
     * 注册RpcBeanDefinition
     *
     * @param annotation
     * @param bean
     * @param beanName
     * @param injectedType
     * @param injectName
     */
    private void registerRpcBeanDefinition(Annotation annotation, Object bean, String beanName, Class<?> injectedType, String injectName) {
        BeanDefinition definition = buildRpcBeanDefinition(annotation, bean, beanName, injectedType, injectName);
        beanDefinitionRegistry.registerBeanDefinition(injectName, definition);
    }

    @Override
    public void setBeanClassLoader(ClassLoader classLoader) {
        this.classLoader = classLoader;
    }


    /**
     * Rpc注入对象元数据
     */
    private class RpcInjectionMetadata extends InjectionMetadata {

        public RpcInjectionMetadata(Class<?> targetClass, Collection<InjectedElement> elements) {
            super(targetClass, elements);
        }
    }


    private boolean isAssignable(String beanClass, Class<?> supClass) {
        try {
            Class<?> subClass = ClassUtils.forName(beanClass, classLoader);
            return ClassUtils.isAssignable(supClass, subClass);
        } catch (ClassNotFoundException e) {
            return false;
        }
    }


    /**
     * 字段注入元数据元素
     */
    private class RpcFieldElement extends InjectionMetadata.InjectedElement {

        private final Field field;

        private final boolean required;

        private final Annotation annotation;

        private volatile boolean cached = false;

        private volatile Object cachedValue;

        public RpcFieldElement(Field field, Annotation annotation, boolean required) {
            super(field, null);
            this.field = field;
            this.required = required;
            this.annotation = annotation;
        }

        @Override
        protected void inject(Object bean, String beanName, PropertyValues pvs) throws Throwable {
            Object value;
            //已经建立过关联关系的bean依赖
            if (cached) {
                value = resolvedCachedArgument(beanName, this.cachedValue);
                //赋值
                if (value != null) {
                    ReflectionUtils.makeAccessible(field);
                    field.set(bean, value);
                }
                return;
            }

            //查找是否有bean注册信息
            String injectName = resolveBeanName(field, field.getType(), annotation);
            boolean isExist = beanFactory.containsBeanDefinition(injectName);
            boolean isServiceBean = false;

            if (!isExist) {
                //注册bean 的信息
                registerRpcBeanDefinition(annotation, bean, beanName, field.getType(), injectName);
                Object bean1 = beanFactory.getBean(injectName, field.getType());
            } else {
                BeanDefinition beanDefinition = beanFactory.getBeanDefinition(injectName);
                String beanClassName = beanDefinition.getBeanClassName();
                if (isAssignable(beanClassName, RpcServiceBean.class)) {
                    isServiceBean = true;
                }
            }
            //添加注入类和注入类的依赖关系
            RpcDependencyDescriptor desc = new RpcDependencyDescriptor(field, isServiceBean, this.required);
            desc.setContainingClass(bean.getClass());
            //获取依赖beanName
            Set<String> dependentBeanNames = new LinkedHashSet<>(1);
            Assert.state(beanFactory != null, "No BeanFactory available");
            TypeConverter typeConverter = beanFactory.getTypeConverter();
            try {
                value = beanFactory.resolveDependency(desc, beanName, dependentBeanNames, typeConverter);
            } catch (BeansException ex) {
                throw new UnsatisfiedDependencyException(null, beanName, new InjectionPoint(field), ex);
            }
            //建立依赖关系并缓存依赖


            synchronized (this) {
                if (!this.cached) {
                    if (value != null || this.required) {
                        registerDependentBeans(beanName, dependentBeanNames);
                        if (dependentBeanNames.size() == 1) {
                            String dependentBeanName = dependentBeanNames.iterator().next();
                            Class dependentType = isServiceBean ? ((RpcServiceBean) value).getRpcServiceInterface() : field.getType();
                            if (beanFactory.containsBean(dependentBeanName) && beanFactory.isTypeMatch(dependentBeanName, dependentType)) {
                                cachedValue = new ShortcutDependencyDescriptor(desc, dependentBeanName, desc.isServiceBean(), dependentType);
                            }
                        }
                    } else {
                        cachedValue = null;
                    }
                    this.cached = true;
                }
            }
            //赋值
            if (value != null) {
                ReflectionUtils.makeAccessible(field);

                if (isServiceBean) {
                    field.set(bean, ((RpcServiceBean) value).getRpcService());
                } else {
                    field.set(bean, value);
                }


            }
        }
    }

    /**
     * 方法注入元数据元素
     */
    private class RpcMethodElement extends InjectionMetadata.InjectedElement {

        private final boolean required;

        private final Method method;

        private final Annotation annotation;

        private volatile boolean cached = false;

        private volatile Object[] cachedValue;

        public RpcMethodElement(Method method, PropertyDescriptor descriptor, boolean required, Annotation annotation) {
            super(method, descriptor);
            this.required = required;
            this.method = method;
            this.annotation = annotation;
        }

        @Override
        protected void inject(Object bean, String beanName, PropertyValues pvs) throws Throwable {
            //检查注入属性是否跳过防止多次注入
            if (checkPropertySkipping(pvs)) {
                return;
            }
            Object[] arguments;
            if (this.cached) {
                //查找建立过关系的依赖参数
                arguments = resolveCachedArguments(beanName);
                //赋值
                if (arguments != null) {
                    ReflectionUtils.makeAccessible(method);
                    method.invoke(bean, arguments);
                }
                return;
            }
            //查找是否有bean注册信息
            Class<?> injectedType = pd.getPropertyType();
            String injectName = resolveBeanName(member, injectedType, annotation);
            boolean isExist = beanFactory.containsBeanDefinition(injectName);

            boolean isServiceBean = false;

            if (!isExist) {
                //注册bean 的信息
                registerRpcBeanDefinition(annotation, bean, beanName, injectedType, injectName);
            } else {
                BeanDefinition beanDefinition = beanFactory.getBeanDefinition(injectName);
                String beanClassName = beanDefinition.getBeanClassName();
                if (isAssignable(beanClassName, RpcServiceBean.class)) {
                    isServiceBean = true;
                }
            }

            //根据方法反射查找需要注入的Bean
            int argumentCount = method.getParameterCount();
            arguments = new Object[argumentCount];
            RpcDependencyDescriptor[] descriptors = new RpcDependencyDescriptor[argumentCount];
            Set<String> dependentBeanNames = new LinkedHashSet<>(argumentCount);
            Assert.state(beanFactory != null, "No BeanFactory available");
            TypeConverter typeConverter = beanFactory.getTypeConverter();

            for (int i = 0; i < arguments.length; i++) {
                MethodParameter methodParam = new MethodParameter(method, i);
                RpcDependencyDescriptor currDesc = new RpcDependencyDescriptor(methodParam, isServiceBean, this.required);
                currDesc.setContainingClass(bean.getClass());
                descriptors[i] = currDesc;
                try {
                    Object arg = beanFactory.resolveDependency(currDesc, beanName, dependentBeanNames, typeConverter);
                    if (arg == null && !this.required) {
                        arguments = null;
                        break;
                    }
                    arguments[i] = arg;
                } catch (BeansException ex) {
                    throw new UnsatisfiedDependencyException(null, beanName, new InjectionPoint(methodParam), ex);
                }
            }
            //建立依赖关系并缓存依赖
            synchronized (this) {
                if (!cached) {
                    if (arguments != null) {
                        DependencyDescriptor[] cachedMethodArguments = new DependencyDescriptor[arguments.length];
                        registerDependentBeans(beanName, dependentBeanNames);
                        if (dependentBeanNames.size() == argumentCount) {
                            Iterator<String> it = dependentBeanNames.iterator();
                            Class<?>[] paramTypes = method.getParameterTypes();
                            for (int i = 0; i < paramTypes.length; i++) {
                                String dependentBeanName = it.next();
                                Class dependentType;
                                //替换参数 并校验bean class
                                if (descriptors[i].isServiceBean()) {
                                    RpcServiceBean serviceBean = (RpcServiceBean) arguments[i];
                                    arguments[i] = serviceBean.getRpcService();
                                    dependentType = serviceBean.getRpcServiceInterface();
                                }else {
                                    dependentType = paramTypes[i];
                                }
                                if (beanFactory.containsBean(dependentBeanName) && beanFactory.isTypeMatch(dependentBeanName,dependentType)) {
                                    cachedMethodArguments[i] = new ShortcutDependencyDescriptor(descriptors[i], dependentBeanName, descriptors[i].isServiceBean(), paramTypes[i]);
                                } else {
                                    cachedMethodArguments[i] = descriptors[i];
                                }
                            }
                        }
                        this.cachedValue = cachedMethodArguments;
                    } else {
                        this.cachedValue = null;
                    }
                    this.cached = true;
                }
            }

            //赋值
            if (arguments != null) {
                try {
                    ReflectionUtils.makeAccessible(method);
                    method.invoke(bean, arguments);
                } catch (InvocationTargetException ex) {
                    throw ex.getTargetException();
                }
            }
        }


        private Object[] resolveCachedArguments(@Nullable String beanName) {
            Object[] cachedMethodArguments = this.cachedValue;
            if (cachedMethodArguments == null) {
                return null;
            }
            Object[] arguments = new Object[cachedMethodArguments.length];
            for (int i = 0; i < arguments.length; i++) {
                arguments[i] = resolvedCachedArgument(beanName, cachedMethodArguments[i]);
            }
            return arguments;
        }
    }

    /**
     * 快速获依赖关系的bean
     */
    private class ShortcutDependencyDescriptor extends DependencyDescriptor {

        private final String shortcut;

        private final boolean isServiceBean;

        private final Class<?> requiredType;

        public ShortcutDependencyDescriptor(DependencyDescriptor original, String shortcut, boolean isServiceBean, Class<?> requiredType) {
            super(original);
            this.shortcut = shortcut;
            this.requiredType = requiredType;
            this.isServiceBean = isServiceBean;
        }

        @Override
        public Object resolveShortcut(BeanFactory beanFactory) {
            if (isServiceBean) {
                RpcServiceBean bean = beanFactory.getBean(this.shortcut, RpcServiceBean.class);
                return bean.getRpcService();
            }
            return beanFactory.getBean(this.shortcut, this.requiredType);
        }
    }


    private static class RpcDependencyDescriptor extends DependencyDescriptor {

        private final boolean isWrapper;

        public RpcDependencyDescriptor(Field field, boolean isWrapper, boolean required) {
            super(field, required);
            this.isWrapper = isWrapper;
        }


        public RpcDependencyDescriptor(MethodParameter parameter, boolean isWrapper, boolean required) {
            super(parameter, required);
            this.isWrapper = isWrapper;
        }


        @Override
        public Class<?> getDependencyType() {
            if (isWrapper) {
                return RpcServiceBean.class;
            }
            return super.getDependencyType();
        }


        public boolean isServiceBean() {
            return isWrapper;
        }

    }

}
